﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Rung.Net.Enums;
using Rung.Net.Events;
using System.IO;

namespace Rung.Net.Cache
{
    public class CacheServerStream : CacheStream, IDisposable
    {
        FileStream fs;
        object OBJ_OnReceive = new object();
        object OBJ_write_buff = new object();
        object OBJ_write_file = new object();
        object OBJ_write_package_buff = new object();
        public event CacheProcessChangedServerEventHandler OnCacheProcessChangedServer;
        private int preIndex;
        /// <summary>
        /// 包缓存。
        /// NetworkStream 是不稳定的数据流，服务端发送的字节不一定一次就被客户端全部接受。
        /// 所以这里开辟两个包大小的缓存空间，用来缓存接收的数据。
        /// 当一次接收没有接到完整的一个包时，将数据缓存起来等待下一个流的到来；
        /// 当下一个流到来后，将数据累积到缓存，并且判断缓存中的数据是否够一个包的长度；
        /// 若够，则立马取出一个包长度的数据使用掉，然后包中剩余的数据必然不够一个包，继续等待下个流；
        /// 若不够，则继续等待下个流。
        /// </summary>
        private byte[] packageBuffer;
        /// <summary>
        /// 当前包缓存中有效数据的长度。
        /// </summary>
        private int packageBufferValidLength;

        public CacheServerStream(System.Net.Sockets.NetworkStream ns, System.Net.EndPoint ep)
            : base(ns, ep) 
        {
            this.packageBuffer = new byte[2 * base.NetBasePackageMaxLength];
            this.packageBufferValidLength = 0;
        }

        protected override void ReceivedRaw(byte[] bytes)
        {
            if (bytes.Length < 4) return;

            // 标记当前繁忙
            base.IsBusy = true;

            base.ReceivedRaw(bytes);

            int type = BitConverter.ToInt32(bytes, 0);
            byte[] bBody = new byte[bytes.Length - IntLength];
            System.Buffer.BlockCopy(bytes, IntLength, bBody, 0, bBody.Length);
            string body = NetUtility.C(bBody);

            switch (type)
            {
                case MessageTypeBase.Cache_PackageMaxLength1:// 接收方收到发送方发来的握手请求（一次握手）
                    {
                        int body0, body1;
                        #region 解析“一次握手”包
                        List<string> bodys = body.Split(LevelSeperators.Level1).ToList();
                        if (bodys.Count != 3)
                            throw (new Exception("数据传输初始化错误（2）。"));
                        else
                        {
                            if (!int.TryParse(bodys[0], out body0) || !int.TryParse(bodys[1], out body1))
                                throw (new Exception("数据传输初始化错误（2）。"));
                            // 发送方和接收方的包容量选择一个较小的
                            if (body0 < base.NetBasePackageMaxLength) base.NetBasePackageMaxLength = body0;
                            // 在接收方创建待保存接收文件的临时文件
                            this.tempFile = NetUtility.GetNewTempFile(body1);
                            // 获取待发送文件在发送方文件系统中的原文件名
                            this.oriFileName = bodys[2];
                        }
                        #endregion

                        base.packageCountReceived = 0;
                        this.preIndex = -1;
                        base.fileLength = body1;

                        #region 构造发送“二次握手”包
                        byte[] data = new byte[2 * IntLength];
                        System.Buffer.BlockCopy(BitConverter.GetBytes(MessageTypeBase.Cache_PackageMaxLength2), 0, bBody, 0, IntLength);
                        System.Buffer.BlockCopy(BitConverter.GetBytes(this.NetBasePackageMaxLength), 0, bBody, IntLength, IntLength);
                        base.SendRaw(bBody);
                        #endregion
                    }
                    break;
                case MessageTypeBase.Cache_CachePackage:// 接收方接受到数据
                default:// 数据片段
                    lock (OBJ_OnReceive)
                    {
                        #region 将接受到的数据累积到包缓存中
                        lock (OBJ_write_package_buff)
                        {
                            System.Buffer.BlockCopy(bytes, 0, this.packageBuffer, this.packageBufferValidLength, bytes.Length);
                            this.packageBufferValidLength += bytes.Length;
                        }
                        #endregion

                        // 尝试处理
                        this.TryParsePackageBuffer();
                    }
                    break;
            }
        }

        private void TryParsePackageBuffer()
        {
            // 缓存数据长度不足一个包长度，则等待继续接受新数据。
            if (this.packageBufferValidLength < base.NetBasePackageMaxLength) return;

            // 数据包类型
            int type = BitConverter.ToInt32(this.packageBuffer, 0);
#if DEBUG
            if (type != MessageTypeBase.Cache_CachePackage)
                throw (new Exception("缓存无效！"));
#endif
            // 每个包实际容纳的文件数据长度（抛去包头，包头包括【数据包类型】【总包数】【包索引】【CRC校验值】四个 int 值）
            int realLength = this.NetBasePackageMaxLength - 4 * IntLength;
            // 每个包实际容纳的文件数据
            this.buffer = new byte[realLength];
            // 总包数
            int packageCount = BitConverter.ToInt32(this.packageBuffer, IntLength);// 跳过【数据包类型】
            // 包索引
            int index = BitConverter.ToInt32(this.packageBuffer, 2 * IntLength);// 跳过【数据包类型】、【总包数】
            // CRC校验值
            uint crc = BitConverter.ToUInt32(this.packageBuffer, 3 * IntLength);
            // 是否为最后一包
            bool isLastPackage = packageCount == index + 1;
            // 本次接收的包是否损毁
            bool isDamaged = false;

            #region 保存数据

            #region 保证 fs（FileStream）可用
            if (fs == null)
            {
                fs = tempFile.OpenWrite();
            }
            if (fs.CanWrite == false)
            {
                fs.Close();
                fs = tempFile.OpenWrite();
            }
            #endregion

            try
            {
                //fs.Position = index * realLength;// 可以去掉？？？？？？？？？？？？？？？？？？？？？？？？？？

                // 预期的正文长度
                int expectContentLength;
                #region 计算获取【预期的正文长度】
                expectContentLength = realLength;
                if (isLastPackage)// 若为最后一包，则需要修正【预期的正文长度】
                {
                    // 根据文件长度计算得到的真正的预期长度
                    expectContentLength = (int)(base.fileLength - (packageCount - 1) * realLength);
                } 
                #endregion

                this.CheckSaveResend(index, expectContentLength, crc, ref isDamaged);
            }
            catch
            {
                this.DisposeFileStream();
                throw;
            }
            #endregion

            // 若该包正确接收，则准备下一包
            if (!isDamaged)
            {
                base.packageCountReceived++;
                this.preIndex++;

                if (packageCountReceived == packageCount)// 若最后一包接受完，则关闭文件流
                    this.DisposeFileStream();

                #region 向发送方发送“已接受”通知
                if (this.OnCacheProcessChangedServer != null)
                {
                    this.OnCacheProcessChangedServer(this, new CacheProcessChangedServerEventArgs(this.Guid, packageCount, index, base.packageCountReceived));
                }
                #endregion

                // 标记当前清闲
                base.IsBusy = false;

                if (packageCountReceived == packageCount)// 若最后一包接受完，则发起完成的通知事件
                    base.DoRaiseOnReceived();
            }
        }

        /// <summary>
        /// 将指定的数组无条件的发往网络流中。
        /// </summary>
        internal void SendBytes(byte[] data)
        {
            this.SendRaw(data);
        }

        /// <summary>
        /// 干掉该对象的文本流变量（this.fs）。
        /// </summary>
        private void DisposeFileStream()
        {
            fs.Close();
            fs.Dispose();
        }

        /// <summary>
        /// 检测包是否损毁，若未损毁则保存。
        /// </summary>
        /// <param name="bytes">本次收到的完整的数据流（包括包头、包体）。</param>
        /// <param name="index">本次包索引。</param>
        /// <param name="expectContentLength">根据文件长度计算得到的真正的预期长度。</param>
        /// <param name="isDamaged">是否损毁。</param>
        private void CheckSaveResend(int index, int expectContentLength, uint crc, ref bool isDamaged)
        {
            // 一个完整包的包头长度
            int packageHeadLength = 4 * IntLength;
            // 根据数据包长度计算得的正文长度
            int contentLength = base.NetBasePackageMaxLength - packageHeadLength;
            // 68705593：全 0 的 CRC 值。
            // CRC校验值（若为最后一包，bContent 尾部的剩余空间已用 0 补齐）
            if (crc != CRC32.Crc(this.packageBuffer, packageHeadLength, contentLength))
            {
                isDamaged = true;
                NetUtility.Log("LogS.txt", string.Format("isDamaged\t{0}\r\n", index));
            }
            else
            {
                lock (OBJ_write_buff)
                {
                    System.Buffer.BlockCopy(this.packageBuffer, packageHeadLength, this.buffer, 0, contentLength); 
                }

                lock (OBJ_write_file)
                {
                    #region 文件流跳到 index 位置
                    long position = index * (base.NetBasePackageMaxLength - packageHeadLength);
                    if (position == fs.Length)
                    {
                        NetUtility.Log("LogS.txt", string.Format("文件到尾\t{0}\r\n", index));
                        return;
                    }
                    if (fs.Position != position)
                    {
                        NetUtility.Log("LogS.txt", string.Format("指针异常\t{0}\r\n", index));
                        fs.Position = position;
                    }
                    #endregion
#if DEBUG
                    if (this.IsBytesAre0(buffer))
                    {
                        NetUtility.Log("LogS.txt", string.Format("零包\t{0}\r\n", index));
                    }
#endif
                    // 写文件
                    fs.Write(this.buffer, 0, expectContentLength);

                    #region 将包缓存中已保存到文件的数据移除（进行类似队列的位移）
                    lock (OBJ_write_package_buff)
                    {
                        // this.packageBuffer 的临时变量
                        byte[] bTemp = new byte[2 * base.NetBasePackageMaxLength];
                        // 将剩余数据块复制到该临时变量
                        System.Buffer.BlockCopy(this.packageBuffer, base.NetBasePackageMaxLength, bTemp, 0, this.packageBufferValidLength);
                        // 临时变量启用
                        this.packageBuffer = bTemp;
                        // 重新计算包缓存中有效数据的长度 
                        this.packageBufferValidLength -= base.NetBasePackageMaxLength;
                    }
                    #endregion
                }
                isDamaged = false;
            }

            // 如果有损毁，请求重新发送
            if (isDamaged)
            {
                // 请求重发
                this.FeedbackResend(index);
                NetUtility.Log("LogS.txt", string.Format("重发\t{0}\r\n", index));
            }
        }

        private bool IsBytesAre0(byte[] bContent)
        {
            for (int i = 0; i < bContent.Length; i++)
            {
                if (bContent[i] != 0) return false;
            }
            return true;
        }

        /// <summary>
        /// 请求重新发送指定索引的包。
        /// </summary>
        private void FeedbackResend(int index)
        {
            byte[] data = new byte[2 * IntLength];
            NetUtility.BlockCopy(BitConverter.GetBytes(MessageTypeBase.Cache_Resend), data, 0);
            NetUtility.BlockCopy(BitConverter.GetBytes(index), data, IntLength);

            this.SendBytes(data);
        }

        #region IDisposable 成员

        public void Dispose()
        {
            base.Dispose();

            if (fs != null)
            {
                this.DisposeFileStream();
            }
        }

        #endregion
    }
}
